package com.site.common.config;

import com.site.common.SpringContextUtils;
import com.site.common.annotation.Log;
import com.site.common.annotation.RedissonLock;
import com.site.common.aspect.LogAspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 切面 限流、防止xss攻击
 */
@Order(-1)
@Slf4j
@Aspect
@Component
public class AspectAdviceConfig {

    @Around("execution(* com.site.controller..*(..))")
    public Object handleAspect(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
        // Login验证
        /*if (method.isAnnotationPresent(Login.class)){
            new LoginAspect().doHandlerAspect(joinPoint,method,request);
        }*/
        // 弃用了，改在过滤器中进行拦截
        /*// 是否限流
        if (method.isAnnotationPresent(AccessLimit.class)){
            new RateLimiterAspect().doHandlerAspect(joinPoint,method,request);
        }
        // 是否需要拦截xss攻击
        if (method.isAnnotationPresent(ParamXssPass.class)){
            new ParamXssPassAspect().doHandlerAspect(joinPoint, method, request);
        }*/
        Object proceed;
        Object[] args = joinPoint.getArgs();
        // 是否分布式锁
        if (method.isAnnotationPresent(RedissonLock.class)){
            proceed = lock(joinPoint,method,args);
        }else {
            proceed = joinPoint.proceed(args);
        }
        long time = System.currentTimeMillis() - startTime;
        log.info("接口地址{},请求方式{},参数{},时长{}毫秒",request.getRequestURI(),request.getMethod(),args,time);
        // 是否需要记录日志
        if (method.isAnnotationPresent(Log.class)){
            new LogAspect().doHandlerAspect(joinPoint, method, request);
        }
        return proceed;
    }

    /**
     * 加锁执行
     * @param joinPoint
     * @param method
     * @return
     * @throws Throwable
     */
    private Object lock(ProceedingJoinPoint joinPoint,Method method,Object[] args) throws Throwable{
        RedissonClient redissonClient = SpringContextUtils.getBean(RedissonClient.class);
        Assert.notNull(redissonClient,"RedissonClient Get is Null");
        RedissonLock redissonLock = method.getAnnotation(RedissonLock.class);
        String lockKey = getLockKey(joinPoint, method, redissonLock,args);
        RLock lock = redissonLock.fairLock() ? redissonClient.getFairLock(lockKey) : redissonClient.getLock(lockKey);
        try {
            boolean lockFlag = lock.tryLock(redissonLock.waitTime(), redissonLock.leaseTime(), redissonLock.timeUnit());
            if (!lockFlag){
                log.warn("get lock failed : " + lockKey);
                return null;
            }
            return joinPoint.proceed(args);
        }catch (Exception e){
            log.error("execute locked method occured an exception",e);
            return null;
        }finally {
            if (lock.isHeldByCurrentThread() && lock.isLocked()){
                lock.unlock();
                log.info("lock {} has been released",lockKey);
            }
        }
    }

    /**
     * 获取锁的key
     * @param joinPoint
     * @param method
     * @return
     */
    private String getLockKey(ProceedingJoinPoint joinPoint, Method method, RedissonLock redissonLock,Object[] args){
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = method.getName();
        String separator = redissonLock.separator();
        return redissonLock.prefix().concat(className).concat(separator).concat(methodName).concat(separator).concat(parse(redissonLock.key(),method,args));
    }

    /**
     * 解析spring EL表达式
     * @param key    key
     * @param method method
     * @param args   args
     * @return parse result
     */
    private ExpressionParser parser = new SpelExpressionParser();
    private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
    private String parse(String key, Method method, Object[] args) {
        String[] params = discoverer.getParameterNames(method);
        if (null == params || params.length == 0 || !key.contains("#")) {
            return key;
        }
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < params.length; i++) {
            if (null != args[i]){
                context.setVariable(params[i], args[i]);
            }else {
                context.setVariable(params[i],"");
            }
        }
        return parser.parseExpression(key).getValue(context, String.class);
    }
}
